#include <PopupOSAL.hpp>
#include <iostream>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/tcp.h>

using namespace std;
using namespace PopupUtils;

Socket::Socket() : s(0) {

  // UDP: use SOCK_DGRAM instead of SOCK_STREAM
  s = socket(AF_INET,SOCK_STREAM,0);

  if (s == -1) {
    throw Socket::Exception("Socket instantiation failed");
  }

  refCounter = new int(1);
}

Socket::Socket(OSSocketFd s) : s(s) {
  refCounter = new int(1);
};

Socket::~Socket() {
  if (! --(*refCounter)) {
    Close();
    delete refCounter;
  }
}

Socket::Socket(const Socket& o) {
  refCounter=o.refCounter;
  (*refCounter)++;
  s         =o.s;
}

Socket& Socket::operator=(Socket& o) {
  (*o.refCounter)++;

  refCounter=o.refCounter;
  s         =o.s;

  return *this;
}

void Socket::Close() {
  shutdown(s, 2);
  close(s);
}
/*
std::string Socket::ReceiveLine() {
  std::string ret;
  bool _true = true;

  while (_true) {
    char r;

    switch(recv(s, &r, 1, 0)) {
      case 0: // not connected anymore;
              // ... but last line sent
              // might not end in \n,
              // so return ret anyway.
        return ret;
      case -1:
        return "";
	//      if (errno == EAGAIN) {
	//        return ret;
	//      } else {
	//      // not connected anymore
	//      return "";
	//      }
    }

    ret += r;
    if (r == '\n')  return ret;
  }

  return ret;
}

void Socket::SendLine(std::string s) {
  s += '\n';
  send(s,s.c_str(),s.length(),0);
}

void Socket::SendBytes(const std::string& s) {
  send(s,s.c_str(),s.length(),0);
}
*/
bool Socket::SendBytes(const unsigned char *p_buff, size_t p_size)
{
  bool  _rc = true;
  size_t _remainingBytes = p_size;
  int _nbBytesSent = 0;

  const unsigned char *_ptr = p_buff;

  while (_remainingBytes > 0)
  {
    _nbBytesSent = send (s, (const char*) _ptr, _remainingBytes, 0);
    if (_nbBytesSent <= 0)
    {
      _nbBytesSent = 0;
      _rc = false; // Fucking errors to manage
    }
    else
    {
      _remainingBytes -= _nbBytesSent;
      _ptr = _ptr + _nbBytesSent;
    }
  }
  return _rc;

  //send(s, p_buff, p_size, 0);
}

bool Socket::ReceiveBytes(char *p_buff, size_t p_size = 0)
{
  bool  _rc = true;
  size_t _remainingBytes = p_size;
  int _nbBytesRead = 0;

  char *_ptr = p_buff;
	  
  while (_remainingBytes > 0)
  {
    _nbBytesRead = recv (s, (char*) _ptr, _remainingBytes, 0);
    if (_nbBytesRead <= 0)
    {
      _remainingBytes = 0;
      _rc = false; // Fucking errors to manage
    }
    else
    {
      _remainingBytes -= _nbBytesRead;
      _ptr = _ptr + _nbBytesRead;
    }
  }
  return _rc;
}

bool Socket::enableKeepAlive(bool p_doEnable, int p_timeout)
{
  int _val = 0;
  bool _rc = true;
  
  if (p_doEnable == true)
  {
    _val = 1;
    setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &_val, sizeof(int));

#ifdef _LINUX
    _val = p_timeout; // Timeout after which keep alive kicks in
    setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE, &_val, sizeof(int));

    _val = 2; // 2 seconds between each probe
    setsockopt(s, IPPROTO_TCP, TCP_KEEPINTVL, &_val, sizeof(int));

    _val = 2; // 2 probes before dropping connection
    setsockopt(s, IPPROTO_TCP, TCP_KEEPCNT, &_val, sizeof(int));

#else
#ifdef _MACOS
    _val = p_timeout; // Timeout after which keep alive kicks in
    setsockopt(s, IPPROTO_TCP, TCP_KEEPALIVE, &_val, sizeof(int));

#else
    _rc = false;
#endif
#endif
  }
  else
  {
    _val = 0;
    setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &_val, sizeof(int));
  }

  return _rc;
}
/*
void Socket::SendMessage(size_t p_size, char *p_buff)
{
  size_t _remainingBytes = p_size;
  int _nbBytesSent;
  char *_ptr = p_buff;

  while (_remainingBytes > 0)
  {
    _nbBytesSent = send(s, _ptr, _remainingBytes, 0);
    if (_nbBytesSent <= 0)
    {
      _remainingBytes = 0;
      // Error to manage
    }
    else
    {
      _remainingBytes -= _nbBytesSent;
      _ptr += _nbBytesSent;
    }
  }
}

*/
SocketServer::SocketServer(int port, int connections, Socket::Mode type) {
  sockaddr_in sa;

  memset(&sa, 0, sizeof(sa));

  sa.sin_family = PF_INET;             
  sa.sin_port = htons(port);          
  s = socket(AF_INET, SOCK_STREAM, 0);
  if (s == -1) {
    throw Socket::Exception("Failed to create socket");
  }

  if (type == NonBlockingSocket) {
    fcntl(s, O_NONBLOCK);
  }

  // Set SO_LINGER option to avoid leaving sockets in TIME_WAIT state
  // for a too long time
  int _val = 1;
  setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &_val, sizeof(int));

  /* bind the socket to the internet address */
  if (bind(s, (sockaddr *)&sa, sizeof(sockaddr_in)) != 0) {
    close(s);
    throw Socket::Exception("Failed to bind socket");
  }
  else
  {
    listen(s, connections);
  }  
}

Socket* SocketServer::Accept() {
  int new_sock = accept(s, 0, 0);
  if (new_sock == -1) {
    throw Socket::Exception("accept() failed");
  }

  Socket* r = new Socket(new_sock);
  return r;
}

SocketClient::SocketClient(const std::string& host, int port) : Socket() {
  std::string error;

  struct hostent *he;
  if ((he = gethostbyname(host.c_str())) == 0) {
    throw Socket::Exception(strerror(errno));
  }

  sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr = *((in_addr *)he->h_addr);
  memset(&(addr.sin_zero), 0, 8); 

  if (::connect(s, (sockaddr *) &addr, sizeof(sockaddr))) {
    throw Socket::Exception(strerror(errno));
  }
}
